package com.oddpointcc.szpt.accounting.component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.UriTemplate;

/**
 * 面包屑拦截器
 * 
 * @author yzx
 *
 */
public class BreadCrumbInterceptor extends HandlerInterceptorAdapter {

	private static final String CURRENT_BREAD_CRUMB = "currentBreadCrumb";
	private static final String BREAD_CRUMB_LINKS = "breadCrumb";

	@Autowired
	private RequestMappingHandlerMapping requestMappingHandlerMapping;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		if (handler instanceof HandlerMethod == false) {
			return true;
		}
		if (isAjaxRequest(request)) {
			return true;
		}
		HttpSession session = request.getSession();
		emptyCurrentBreadCrumb(session);
		Annotation annotation = getLinkAnnotation((HandlerMethod) handler);
		if (annotation != null) {
			processAnnotation(request, session, annotation);
		}
		return true;
	}

	/**
	 * 处理当前面包屑
	 * 
	 * @param request
	 * @param session
	 * @param annotation
	 */
	private void processAnnotation(HttpServletRequest request, HttpSession session, Annotation annotation) {
		Map<String, LinkedHashMap<String, BreadCrumbLink>> breadCrumb = getBreadCrumbLinksFromSession(session);
		if (breadCrumb == null) {
			breadCrumb = createBreadCrumbLinks();
		}

		Link link = (Link) annotation;
		String label = link.label();

		LinkedHashMap<String, BreadCrumbLink> familyMap = breadCrumb.get(link.family());

		List<BreadCrumbLink> breadCrumbLinks = new LinkedList<>();
		generateBreadCrumbsRecursively(familyMap.get(label), breadCrumbLinks);

		Map<?, ?> pathVariables = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);

		BreadCrumbView currentBreadCrumb = new BreadCrumbView(label,
				breadCrumbLinks.stream()
						.collect(Collectors.toMap(BreadCrumbLink::getLabel,
								object -> filterAndCreateUrl(object.getUrlPatterns(), pathVariables), (u, v) -> v,
								LinkedHashMap::new)));
		session.setAttribute(CURRENT_BREAD_CRUMB, currentBreadCrumb);
	}

	/**
	 * 创建面包屑链接信息
	 * 
	 * @return
	 */
	public Map<String, LinkedHashMap<String, BreadCrumbLink>> createBreadCrumbLinks() {
		List<BreadCrumbLink> breadCrumbLinks = new LinkedList<>();
		// 获取RequestMapping根据带Link注解创建BreadCrumbLink
		Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
		for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
			RequestMappingInfo requestMappingInfo = entry.getKey();
			HandlerMethod handlerMethod = entry.getValue();
			Annotation annotation = getLinkAnnotation(handlerMethod);
			if (annotation != null) {
				Link link = (Link) annotation;
				BreadCrumbLink breadCrumbLink = new BreadCrumbLink(link.label(), link.parent(), link.family(),
						requestMappingInfo.getPatternsCondition().getPatterns());
				breadCrumbLinks.add(breadCrumbLink);
			}
		}

		// 根据Family分组
		Map<String, List<BreadCrumbLink>> familyMaps = breadCrumbLinks.stream()
				.collect(Collectors.groupingBy(BreadCrumbLink::getFamily));

		// FamilyMap的值构建Tree关系
		for (List<BreadCrumbLink> links : familyMaps.values()) {
			for (BreadCrumbLink node1 : links) {
				for (BreadCrumbLink node2 : links) {
					if (node1.getParentLabel() != null && node1.getParentLabel().equals(node2.getLabel())) {
						node1.setParent(node2);
						node2.getChildren().add(node1);
						break;
					}
				}
			}
		}

		Map<String, LinkedHashMap<String, BreadCrumbLink>> breadCrumb = new HashMap<>();
		// 构建面包屑链接信息
		familyMaps.forEach((s, l) -> {
			breadCrumb.put(s, l.stream().collect(
					Collectors.toMap(BreadCrumbLink::getLabel, Function.identity(), (u, v) -> v, LinkedHashMap::new)));
		});

		return breadCrumb;
	}

	/**
	 * 获取Link注解
	 * 
	 * @param handlerMethod
	 * @return
	 */
	public Annotation getLinkAnnotation(HandlerMethod handlerMethod) {
		Method method = handlerMethod.getMethod();
		Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
		for (Annotation annotation : declaredAnnotations) {
			if (annotation.annotationType().equals(Link.class)) {
				return annotation;
			}
		}
		return null;
	}

	/**
	 * 清空当前面包屑
	 * 
	 * @param session
	 */
	private void emptyCurrentBreadCrumb(HttpSession session) {
		session.removeAttribute(CURRENT_BREAD_CRUMB);
	}

	/**
	 * 获取面包屑链接信息
	 * 
	 * @param session
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Map<String, LinkedHashMap<String, BreadCrumbLink>> getBreadCrumbLinksFromSession(HttpSession session) {
		Map<String, LinkedHashMap<String, BreadCrumbLink>> breadCrumb = (Map<String, LinkedHashMap<String, BreadCrumbLink>>) session
				.getAttribute(BREAD_CRUMB_LINKS);
		return breadCrumb;
	}

	/**
	 * 递归生成面包屑
	 * 
	 * @param link
	 * @param breadCrumbLinks
	 */
	private void generateBreadCrumbsRecursively(BreadCrumbLink link, List<BreadCrumbLink> breadCrumbLinks) {
		if (link.getParent() != null) {
			generateBreadCrumbsRecursively(link.getParent(), breadCrumbLinks);
		}
		breadCrumbLinks.add(link);
	}

	/**
	 * 过滤和生成URL
	 * 
	 * @param urlPatterns
	 * @param pathVariables
	 * @return
	 */
	public String filterAndCreateUrl(Set<String> urlPatterns, Map<?, ?> pathVariables) {
		String url = String.join("", urlPatterns);
		if (pathVariables != null) {
			UriTemplate template = new UriTemplate(url);
			Map<String, String> uriVariables = pathVariables.entrySet().stream()
					.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString()));
			url = template.expand(uriVariables).toString();
		}
		return url;
	}

	/**
	 * 是否是Ajax请求
	 * 
	 * @param request
	 * @return
	 */
	public boolean isAjaxRequest(HttpServletRequest request) {
		return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("x-requested-with"));
	}

}
